﻿using Tao.OpenGl;

using System;
using VectorMath;
using System.Windows.Forms;

namespace Candle
{
    public class PerspectiveProjection
    {
        private int width, height;

        public int Height
        {
            get { return height; }
        }

        public int Width
        {
            get { return width; }
        }
        private double R;
        private double T;
        private double Ba;
        private double eyeX;
        private double eyeY;
        private double eyeZ;
        private double Xrotation;
        private double Yrotation;
        private double d;

        //for threadsafe picking
        double[] model = new double[16];
        double[] proj = new double[16];
        int[] view = new int[4];

        public PerspectiveProjection(int viewportWidth, int viewportHeight, int x, int y, int z)
        {
            width = viewportWidth;
            height = viewportHeight;
            R = x;// *(double)viewportWidth / viewportHeight;
            T = y;
            Ba = z;

            resetCamera();
        }

        internal void Resize(int viewportWidth, int viewportHeight)
        {
            width = viewportWidth;
            height = viewportHeight;
        }

        public void apply_gluPerspective()
        {
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();


            Glu.gluPerspective(45.0f, (double)width / (double)height, 0.1, 2 * d + Ba);

            // / GET READY TO DRAW:
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();

            //for getting artifact perspectives right hardcode these to test
            //location <0.25,0.75,0.99>\nlook_at <0.75,0.25,0.01
            //glu.gluLookAt(0.75,0.75,0.99, 0.25,0.25,0.01, 0, 1, 0);

            Glu.gluLookAt(eyeX, eyeY, eyeZ, R / 2, T / 2, Ba / 2, 0, 1, 0);
            //System.out.printf("{0:G6}, {1:G6}, {2:G6}\n", eyeX, eyeY, eyeZ);

            Gl.glGetIntegerv(Gl.GL_VIEWPORT, view);
            Gl.glGetDoublev(Gl.GL_MODELVIEW_MATRIX, model);
            Gl.glGetDoublev(Gl.GL_PROJECTION_MATRIX, proj);
        }

        public Pnt3D getPoint3d(MouseEventArgs e)
        {
            System.Drawing.Size size = TaoForm.ActiveForm.ClientRectangle.Size;

            //              double x = (double) e.getX() / (double) size.width; 
            //              x *= (r + 2 * eps);
            //              x -= eps;
            //
            //              double y = 1. - (double) e.getY() / (double) size.height;
            //              y *= (1 + 2 * eps);
            //              y -= eps;

            double realy = view[3] - e.Y - 1;
            //realy /= size.height;
            double realx = e.X;
            //realx /= size.width;

            double winx, winy, winz;


            //play with 0.25*d to get the z (distance from camera) value correct
            int result2 = Glu.gluUnProject(realx, realy, -0.25 * eyeZ, model, proj, view, out winx, out winy, out winz);

            Pnt3D p = new Pnt3D(winx, winy, winz);
            System.Console.Out.WriteLine("picked " + p);
            // p.clampMax(1);
            // p.clampMin(0);
            return p;
        }

        public void zoom(double distFactor)
        {
            d *= distFactor;

            Pnt3D center = new Pnt3D(R / 2, T / 2, Ba / 2);
            Pnt3D eye = new Pnt3D(eyeX, eyeY, eyeZ);
            Pnt3D ray = Pnt3D.Difference(eye, center);
            ray.Normalize();
            ray.Times(d);
            ray.Add(center);
            eyeX = ray.x;
            eyeY = ray.y;
            eyeZ = ray.z;
        }

        public void rotate(double xrotation, double yrotation)
        {
            
            Pnt3D center = new Pnt3D(R / 2, T / 2, Ba / 2);
            Pnt3D eye = new Pnt3D(eyeX, eyeY, eyeZ);
            Pnt3D ray = Pnt3D.Difference(eye, center);
            ray.Normalize();

            //first do Y, set x to zero
            ray = rotateX(-Xrotation, center, ray);
            ray = rotateY(yrotation, center, ray);
            ray = rotateX(Xrotation, center, ray);

            //then do X
            ray = rotateX(xrotation, center, ray);

            ray.Times(d);
            ray.Add(center);

            eyeX = ray.x;
            eyeY = ray.y;
            eyeZ = ray.z;

            Xrotation += xrotation;
            Yrotation += yrotation;

            //Console.Out.WriteLine("rotated to {0:G6}, {1:G6}, {2:G6}\n", eyeX, eyeY, eyeZ);
        }

        private Pnt3D rotateX(double xrotation, Pnt3D center, Pnt3D ray)
        {
            double cosx = Math.Cos(xrotation);
            double sinx = Math.Sin(xrotation);
            ray = new Pnt3D(cosx * ray.x - sinx * ray.z, ray.y, sinx * ray.x + cosx * ray.z);
            ray.Normalize();
            return ray;
        }

        private Pnt3D rotateY(double yrotation, Pnt3D center, Pnt3D ray)
        {
            double cosy = Math.Cos(yrotation);
            double siny = Math.Sin(yrotation);
            ray = new Pnt3D(ray.x, cosy * ray.y - siny * ray.z, siny * ray.y + cosy * ray.z);
            ray.Normalize();
            return ray;
        }


        public void resetCamera()
        {
            eyeX = R * 0.5;
            eyeY = T * 0.75;
            d = Math.Max(Math.Max(R,T),Ba) * 3;
            eyeZ = Ba * 0.5 + d;
        }

        private static double[,] MatrixMultiply(double[,] A, double[,] B)
        {
            double[,] res = new double[3, 3];
            for (int i = 0; i < 3; i++)
                for (int j = 0; j < 3; j++)
                    for (int k = 0; k < 3; k++)
                    {
                        res[i, j] += A[i, k] * B[k, j];
                    }
            return res;
        }


    }
}
